﻿using FluentValidation.Results;
using RockBus.ConfigurationService.Interfaces;
using RockBus.ConfigurationService.Validation;
using RockBus.DataContracts;
using RockBus.Hosting.Interfaces;
using RockBus.DataContracts.ConfigurationService;
using RockBus.Logging.Interfaces;
using RockBus.Routing.Interfaces;
using RockBus.SubscriptionRepository.Interfaces;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace RockBus.Routing
{
    public class SubscriptionManager : ISubscriptionManager
    {
        public SubscriptionManager(
            ILogger logger,
            ISubscriptionRepository subscriptionRepository,
            ISubscriptionConfigurationManager subscriptionConfigurationManager,
            IReceivePortServiceHostFactory receivePortServiceHostFactory)
        {
            this.Logger = logger;
            this.SubscriptionRepository = subscriptionRepository;
            this.SubscriptionConfigurationManager = subscriptionConfigurationManager;
            this.ReceivePortServiceHostFactory = receivePortServiceHostFactory;

            this.AddOrUpdatePublishersRequestValidator = new AddOrUpdatePublishersRequestValidator();
            this.AddOrUpdateSubscribersRequestValidator = new AddOrUpdateSubscribersRequestValidator();
            this.RemoveSubscribersRequestValidator = new RemoveSubscribersRequestValidator();
            this.RemoveSubscriptionsRequestValidator = new RemoveClientSubscriptionsRequestValidator();
            this.RemovePublishersRequestValidator = new RemoveClientPublishersRequestValidator();
            this.RemoveReceivePortsRequestValidator = new RemoveClientReceivePortsRequestValidator();

            this.ConfigurationServiceResponse = new RockBusAck();
        }

        private ILogger Logger { get; set; }

        private ISubscriptionRepository SubscriptionRepository { get; set; }

        public  ISubscriptionConfigurationManager SubscriptionConfigurationManager { get; set; }

        private IReceivePortServiceHostFactory ReceivePortServiceHostFactory { get; set; }

        private AddOrUpdatePublishersRequestValidator AddOrUpdatePublishersRequestValidator { get; set; }

        private AddOrUpdateSubscribersRequestValidator AddOrUpdateSubscribersRequestValidator { get; set; }

        private RemoveSubscribersRequestValidator RemoveSubscribersRequestValidator { get; set; }

        private RemoveClientSubscriptionsRequestValidator RemoveSubscriptionsRequestValidator { get; set; }

        private RemoveClientPublishersRequestValidator RemovePublishersRequestValidator { get; set; }

        private RemoveClientReceivePortsRequestValidator RemoveReceivePortsRequestValidator { get; set; }

        private RockBusAck ConfigurationServiceResponse { get; set; }

        #region IConfigurationService

        public SubscriberConfiguration GetSubscriberConfiguration()
        {
            return this.SubscriptionRepository.GetSubscriberConfiguration();
        }

        //public RockBusAck AddOrUpdate(AddOrUpdatePublishersRequest publishersRequest, AddOrUpdateSubscribersRequest subscribersRequest)
        //{
        //    this.AddOrUpdatePublishers(publishersRequest);
        //    return this.AddOrUpdateSubscribers(subscribersRequest);
        //}

        public RockBusAck AddOrUpdateSubscribers(AddOrUpdateSubscribersRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Subscribers: {0}", request.Subscribers.Count);

            var vr = this.AddOrUpdateSubscribersRequestValidator.Validate(request);
            if (!vr.IsValid)
            {
                var ve = this.CreateValidationException(vr);
                this.Logger.LogError(MethodBase.GetCurrentMethod(), "ValidationException: {0}", vr.ToErrorString());
                throw ve;
            }

            var sc = request.CreateSubscriberConfiguration(this.SubscriptionConfigurationManager.StaticSubscriberConfiguration);

            this.SubscriptionRepository.AddOrUpdateSubscriberConfiguration(sc);

            return this.ConfigurationServiceResponse;
        }

        public RockBusAck RemoveSubscribers(RemoveSubscribersRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Subscribers {0}", request.SubscriberIds.Count);

            var rsr = request.CreateRemoveSubscribersRequest(this.SubscriptionConfigurationManager.StaticSubscriberConfiguration);

            var vr = this.RemoveSubscribersRequestValidator.Validate(rsr);
            if (!vr.IsValid)
            {
                var ve = this.CreateValidationException(vr);
                this.Logger.LogError(MethodBase.GetCurrentMethod(), "ValidationException: {0}", vr.ToErrorString());
                throw ve;
            }

            this.SubscriptionRepository.RemoveSubscribers(rsr);

            return this.ConfigurationServiceResponse;
        }

        public RockBusAck RemoveSubscriptions(RemoveSubscriptionsRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "SubscriberId: {0}; Subscriptions {1}", request.SubscriberId, request.SubscriptionIds.Count);

            if (this.SubscriptionConfigurationManager.StaticSubscriberConfiguration.EvaluationElementDictionary.ContainsKey(request.SubscriberId))
            {
                this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "SubscriberId {0} is statically configured, ignoring.", request.SubscriberId);
                return this.ConfigurationServiceResponse;
            }

            var vr = this.RemoveSubscriptionsRequestValidator.Validate(request);
            if (!vr.IsValid)
            {
                var ve = this.CreateValidationException(vr);
                this.Logger.LogError(MethodBase.GetCurrentMethod(), "ValidationException: {0}", vr.ToErrorString());
                throw ve;
            }

            var rscr = new RemoveSubscriptionsRequest(request.SubscriberId, request.SubscriptionIds);
            this.SubscriptionRepository.RemoveSubscriptions(rscr);

            return this.ConfigurationServiceResponse;
        }

        public PublisherConfiguration GetPublisherConfiguration()
        {
            return this.SubscriptionRepository.GetPublisherConfiguration();
        }

        public AddOrUpdatePublishersResponse AddOrUpdatePublishers(AddOrUpdatePublishersRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Publishers: {0}", request.Publishers.Count);

            var vr = this.AddOrUpdatePublishersRequestValidator.Validate(request);
            if (!vr.IsValid)
            {
                var ve = this.CreateValidationException(vr);
                this.Logger.LogError(MethodBase.GetCurrentMethod(), "ValidationException: {0}", vr.ToErrorString());
                throw ve;
            }

            var pc = request.CreatePublisherConfiguration(this.SubscriptionConfigurationManager.StaticPublisherConfiguration);

            // Create ServiceHost, so we can determine its endpoint address
            var endpoints = new List<EndpointInfo>();
            foreach (var publisher in pc.EvaluationElementDictionary.Values)
            {
                foreach (var receivePort in publisher.InfoDictionary.Values)
                {
                     if ((receivePort.ReceiveEndpointInfo != null) &&
                         (!string.IsNullOrEmpty(receivePort.ReceiveEndpointInfo.Address)))
                     {
                         // Create, but do not open; opening the servicehost will be performed by the SelfHostedServiceHostManager or the WebReceivePortServiceHostFactory
                        using (var serviceHostBase = this.ReceivePortServiceHostFactory.CreateServiceHost(receivePort))
                        {
                            var receivePortServiceHost = serviceHostBase as IReceivePortServiceHost;
                            if (receivePortServiceHost != null)
                            {
                                var endpoint = new EndpointInfo(receivePortServiceHost.EndpointInfo);
                                endpoint.Address = receivePortServiceHost.ListenUri.ToString();
                                endpoints.Add(endpoint);
                            }
                        }
                     }
                }
            }

            // Save it in the database
            this.SubscriptionRepository.AddOrUpdatePublisherConfiguration(pc);

            return new AddOrUpdatePublishersResponse(endpoints);
        }

        public RockBusAck RemovePublishers(RemovePublishersRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Publishers: {0}", request.PublisherIds.Count);

            var rpr = request.CreateRemovePublishersRequest(this.SubscriptionConfigurationManager.StaticPublisherConfiguration);

            var vr = this.RemovePublishersRequestValidator.Validate(rpr);
            if (!vr.IsValid)
            {
                var ve = this.CreateValidationException(vr);
                this.Logger.LogError(MethodBase.GetCurrentMethod(), "ValidationException: {0}", vr.ToErrorString());
                throw ve;
            }

            this.SubscriptionRepository.RemovePublishers(rpr);

            return this.ConfigurationServiceResponse;
        }

        public RockBusAck RemoveReceivePorts(RemoveReceivePortsRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "ReceivePorts Count {0}", request.ReceivePortIds.Count);

            if (this.SubscriptionConfigurationManager.StaticPublisherConfiguration.EvaluationElementDictionary.ContainsKey(request.PublisherId))
            {
                this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "PublisherId {0} is statically configured, ignoring.", request.PublisherId);
                return this.ConfigurationServiceResponse;
            }

            var vr = this.RemoveReceivePortsRequestValidator.Validate(request);
            if (!vr.IsValid)
            {
                var ve = this.CreateValidationException(vr);
                this.Logger.LogError(MethodBase.GetCurrentMethod(), "ValidationException: {0}", vr.ToErrorString());
                throw ve;
            }

            var rar = new RemoveReceivePortsRequest(request.PublisherId, request.ReceivePortIds);
            this.SubscriptionRepository.RemoveReceivePorts(rar);

            return this.ConfigurationServiceResponse;
        }

        public RockBusAck RemoveAllSubscribers(RemoveAllSubscribersRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Removing ALL dynamic Subscribers");
            this.SubscriptionRepository.RemoveAllSubscribers(request);
            return this.ConfigurationServiceResponse;
        }

        public RockBusAck RemoveAllPublishers(RemoveAllPublishersRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Removing ALL dynamic Publishers");
            this.SubscriptionRepository.RemoveAllPublishers(request);
            return this.ConfigurationServiceResponse;
        }

        public SubscriberInfo GetSubscriber(GetSubscriberRequest request)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Get subscriber info");
            return SubscriptionRepository.GetSubscriber(request);
        }

        #endregion IConfigurationService

        private ValidationException CreateValidationException(ValidationResult vr)
        {
            var ve = new ValidationException(this.CreateMessage((vr)));
            return ve;
        }

        private string CreateMessage(ValidationResult validationResult)
        {
            var sb = new StringBuilder();
            foreach (ValidationFailure vf in validationResult.Errors)
            {
                sb.AppendLine(vf.ErrorMessage);
            }
            return sb.ToString();
        }

    }
}
